﻿namespace lighttool.tsbb
{
    //BB =BuilderBlock ,建筑块，用来堆叠逻辑的元素
    //块描述
    export class BBDesc
    {
        exitNames: string[];
        name: string;
        inParamNames: string[];
        inParamTypes: string[];
        outParamNames: string[];
        outParamTypes: string[];
    };

    //块
    export interface IBBAction
    {
        desc: BBDesc;
        //返回名字
        getDisplayName(): string;

        //设置入参
        setInParamValue(index: number, value: any): void;
        setInParmaValues(values: any[]): void;
        //获取出参
        getOutParamValue(index: number): any;
        getOutParamValues(): any[];

    }
    //流程块
    export interface IBBAction_Flow extends IBBAction
    {
        //是否结束，返回-1表示为结束
        getExitState(): number;

        onInit(): void;//进入状态时执行
        onExit(): void;//离开状态时执行
        onUpdate(): void;//
    }
    export enum exprReturnValue
    {
        Succ,
        Error,
        InParamNotReady,
    };
    export interface IBBAction_Expr extends IBBAction
    {
        run(): exprReturnValue;//计算结果，true表示结束，false 表示未结束
        getReturnType(valuesInparam:any[]):string;
    }



    export enum bBlockType
    {
        FlowBlock_Begin,    //开始流程块 只有出口
        FlowBlock_End,      //结束流程块 只有入口
        FlowBlock_User,     //
        Block_Expr,//表达式块
    }
    export class paramLink
    {
        blockID: number;//当blockid=-1 的时候，取常数值
        paramOutIndex: number;
        constValue: any;//常数
    }
    export class bBlock
    {
       
        constructor(act: IBBAction)
        {
            if (act != null)
            {
                this.action = act as IBBAction_Flow;
                this.expr = act as IBBAction_Expr;
                if (this.action.onUpdate == undefined)
                    this.action = null;
                if (this.expr.run == undefined)
                    this.expr = null;

            }
            if (this.expr == null && this.action == null)//纯空的
            {
                this.exits = [-1];
            }
            else
            {
                this.exits = [];
                this.inparams = [];




                if (this.action != null)//是流程块
                {
                    this.name = this.action.desc.name;
                    for (var i = 0; i < this.action.desc.inParamNames.length; i++)
                    {
                        this.inparams.push({ blockID: -1, paramOutIndex: -1, constValue: undefined });
                    }
                    for (var i = 0; i < this.action.desc.exitNames.length; i++)
                    {
                        this.exits.push(-1);
                    }

                    this.type = bBlockType.FlowBlock_User;
                    this.exits = [];
                }
                else//是表达式块
                {
                    this.name = this.expr.desc.name;
                    for (var i = 0; i < this.expr.desc.inParamNames.length; i++)
                    {
                        this.inparams.push({ blockID: -1, paramOutIndex: -1, constValue: undefined });
                    }
                    this.type = bBlockType.Block_Expr;
                    this.exits = [-1];
                }
            }
        }
        desc: BBDesc;
        name: string;
        type = bBlockType.FlowBlock_User;
        id: number;//唯一id，自增
        action: IBBAction_Flow;
        expr: IBBAction_Expr;
        exits: number[];//出口连接
        inparams: paramLink[];//入参连接
        x: number = 0;//x位置
        y: number = 0;//y位置

        linkExitTo(index: number, targetBlock: bBlock): void
        {
            this.exits[index] = targetBlock.id;
        }
        linkOutParamTo(index: number, targetBlock: bBlock, targetInParamIndex: number): void
        {
            targetBlock.inparams[targetInParamIndex].blockID = this.id;
            targetBlock.inparams[targetInParamIndex].paramOutIndex = index;
        }
        linkInParamTo(index: number, targetBlock: bBlock, targetOutParamIndex: number): void
        {
            this.inparams[index].blockID = targetBlock.id;
            this.inparams[index].paramOutIndex = targetOutParamIndex;
        }
        getReturnType():string
        {
            return this.expr.getReturnType(this.inparams);
        }
    }
    export class BBFactroyItem
    {
        desc: BBDesc;
        ctor: () => IBBAction;
    }
    export class bbFactory
    {
        private static g_this: bbFactory;
        static instance(): bbFactory
        {
            if (bbFactory.g_this == null)
            {
                bbFactory.g_this = new bbFactory();
            }
            return bbFactory.g_this;
        }
        private mapBBAction: { [id: string]: BBFactroyItem } = {};
        getItemInstance(name: string): IBBAction
        {
            if (this.mapBBAction[name] == undefined) return null;
            var item = this.mapBBAction[name].ctor();
            item.desc = this.mapBBAction[name].desc;
            return item;
        }
        getItemDesc(name: string): BBDesc
        {
            return this.mapBBAction[name].desc;
        }

        regItem(item: BBFactroyItem)
        {
            this.mapBBAction[item.desc.name] = item;
        }
    }
    export class bbplayer
    {
        constructor(beginBlockType: string = "@begin@", endBlockType: string = "@end@")
        {
            if (beginBlockType != null)
            {
                if (beginBlockType != "@begin@")
                {
                    this.beginBlock = this.addBlock(beginBlockType).id;
                }
                else
                {
                    this.beginBlock = this.addBeginBlock();
                }
                this.curBlock = this.getBlock(this.beginBlock);

            }

            if (endBlockType != null)
            {
                if (endBlockType != "@end@")
                {
                    this.endBlock = this.addBlock(endBlockType).id;
                }
                else
                {
                    this.endBlock = this.addEndBlock();

                }
            }

            if (this.curBlock != null && this.endBlock != undefined)
                this.curBlock.exits = [this.endBlock];
        }
        addBlock(name: string): bBlock
        {
            var bb = new bBlock(bbFactory.instance().getItemInstance(name));
            bb.id = this._idBlock;
            this.mapBlock[bb.id] = bb;
            this._idBlock++;
            return bb;
        }
        private addBeginBlock(): number
        {
            var bb = new bBlock(null);
            bb.id = this._idBlock;
            bb.name = "_beginblock";
            bb.type = bBlockType.FlowBlock_Begin;
            bb.action = null;
            this.mapBlock[bb.id] = bb;
            this._idBlock++;
            return bb.id;
        }
        private addEndBlock(): number
        {
            var bb = new bBlock(null);
            bb.id = this._idBlock;
            bb.name = "_endblock";
            bb.type = bBlockType.FlowBlock_End;
            bb.action = null;
            this.mapBlock[bb.id] = bb;
            this._idBlock++;
            return bb.id;
        }
        private curBlock: bBlock;
        private beginBlock: number;
        private endBlock: number;
        private _idBlock: number = 0;
        private mapBlock: { [id: number]: bBlock } = {};
        private isEnd: boolean = false;
        private isRun: boolean = false;
        getBlock(id: number): bBlock
        {
            return this.mapBlock[id];
        }
        getCurBlock(): bBlock
        {
            return this.curBlock;
        }
        getBeginBlock(): bBlock
        {
            return this.getBlock(this.beginBlock);
        }
        getEndBlock(): bBlock
        {
            return this.getBlock(this.endBlock);
        }
        isPlayerEnd(): boolean
        {
            return this.isEnd;
        }
        isPlayerRun(): boolean
        {
            return this.isRun;
        }
        getAllBlockId(): number[]
        {

            var defaultValue = -1;
            var ids: number[] = [];

            for (var key in this.mapBlock)
            {
                ids.push(Number(key));
            }
            return ids;

        }
        private calcFrontParam(b: bBlock)
        {
            for (var i = 0; i < b.inparams.length; i++)
            {
                var p = b.inparams[i];
                if (p.blockID >= 0)
                {
                    var src = this.getBlock(p.blockID);
                    if (src.action != null)
                    {
                        if (src.action.getExitState() < 0)
                            throw new Error("目标流程状态未结束");
                        var v = src.action.getOutParamValue(p.paramOutIndex);
                        if (b.action != null)
                            b.action.setInParamValue(i, v);
                        else if (b.expr != null)
                            b.expr.setInParamValue(i, v);
                    }
                    else if (src.expr != null)
                    {
                        var rv = src.expr.run();
                        if (rv == exprReturnValue.Succ)
                        {
                            var v = src.expr.getOutParamValue(p.paramOutIndex);
                            if (b.action != null)
                                b.action.setInParamValue(i, v);
                            else if (b.expr != null)
                                b.expr.setInParamValue(i, v);
                        }
                        else if (rv == exprReturnValue.InParamNotReady)//依赖表达式未计算，继续往上挖
                        {
                            this.calcFrontParam(src);
                        }
                        else//其他错误
                        {
                            throw new Error("目标表达式状态不能计算");
                        }


                    }
                }
            }
        }
        private changeBlock(target: number)
        {
            if (this.curBlock.action != null)
            {
                this.curBlock.action.onExit();
            }
            this.curBlock = this.getBlock(target);
            if (this.curBlock.id == this.endBlock)
            {
                return;
            }
            for (var i = 0; i < this.curBlock.inparams.length; i++)
            {
                var p = this.curBlock.inparams[i];
                if (p.blockID >= 0)
                {
                    var src = this.getBlock(p.blockID);
                    if (src.action != null)
                    {
                        if (src.action.getExitState() < 0)
                            throw new Error("目标流程状态未结束");
                        var v = src.action.getOutParamValue(p.paramOutIndex);
                        this.curBlock.action.setInParamValue(i, v);
                    }
                    else if (src.expr != null)
                    {
                        var rv = src.expr.run();
                        if (rv == exprReturnValue.Succ)
                        {
                            var v = src.expr.getOutParamValue(p.paramOutIndex);
                            this.curBlock.action.setInParamValue(i, v);
                        }
                        else if (rv == exprReturnValue.InParamNotReady)//依赖表达式未计算，继续往上挖
                        {
                            this.calcFrontParam(src);
                            var v = src.expr.getOutParamValue(p.paramOutIndex);
                            this.curBlock.action.setInParamValue(i, v);
                        }
                        else//其他错误
                        {
                            throw new Error("目标表达式状态不能计算");
                        }


                    }
                }
            }
            if (this.curBlock.action != null)
            {
                this.curBlock.action.onInit();
            }

        }
        update()
        {
            if (this.isRun == false)
            {
                return;
            }
            if (this.curBlock.id == this.endBlock)//是出口
            {
                this.isEnd = true;
                return;//结束了
            }
            if (this.curBlock.id == this.beginBlock)//是入口
            {
                //入口直接走
                this.changeBlock(this.curBlock.exits[0]);
                return;
            }

            if (this.curBlock.action != null)
            {
                this.curBlock.action.onUpdate();
            }
            var id = this.curBlock.action.getExitState();
            if (id >= 0)
            {
                var nextblock = this.curBlock.exits[id];
                this.changeBlock(nextblock);
            }

        }

        //保存到json
        makeJson(): {}
        {
            var _any: any = {};
            _any.beginBlock = this.beginBlock;
            _any.endBlock = this.endBlock;
            _any.mapBlock = {};
            for (var key in this.mapBlock)
            {
                var bb = this.mapBlock[key];
                var b: any = {};
                b.id = bb.id;
                b.name = bb.name;
                b.type = bb.type;
                b.x = bb.x;
                b.y = bb.y;
                b.exits = [];//: number[];//出口连接
                if (bb.exits != null)
                {
                    for (var e = 0; e < bb.exits.length; e++)
                    {
                        b.exits.push(bb.exits[e]);
                    }
                }
                b.inparams = [];//入参
                if (bb.inparams != null)
                {
                    for (var e = 0; e < bb.inparams.length; e++)
                    {
                        var pp = bb.inparams[e];
                        b.inparams.push({ blockID: pp.blockID, paramOutIndex: pp.paramOutIndex, constValue: pp.constValue });
                    }
                }
                _any.mapBlock[b.id] = b;
            }
            return _any;
        }

        //从json加载
        fromJson(json: any): any
        {
            this.mapBlock = {};
            this.beginBlock = json.beginBlock;
            this.endBlock = json.endBlock;
            for (var key in json.mapBlock) {
                var bb = json.mapBlock[key];
                var act = bbFactory.instance().getItemInstance(bb.name);
                var b: bBlock = new bBlock(act);
                b.id = bb.id;
                b.type = bb.type;
                b.name = bb.name;
                b.x = bb.x;
                b.y = bb.y;
                b.exits = [];//: number[];//出口连接
                if (bb.exits != null) {
                    for (var e = 0; e < bb.exits.length; e++) {
                        b.exits.push(bb.exits[e]);
                    }
                }
                b.inparams = [];//入参
                if (bb.inparams != null) {
                    for (var e = 0; e < bb.inparams.length; e++) {
                        var pp = bb.inparams[e];
                        b.inparams.push({ blockID: pp.blockID, paramOutIndex: pp.paramOutIndex, constValue: pp.constValue });
                    }
                }
                this.mapBlock[b.id] = b;

            }
        }
        //仅表示block处于运行状态 需要自己驱动
        start(beginblockid: number = -1)
        {
            if (beginblockid >= 0)
            {
                this.curBlock = this.getBlock(beginblockid);
            }
            this.isRun = true;
        }
        //结束流程
        stop(): void
        {
            this.isRun = false;
        }
    }

}